/*
 * Copyright Konstantin Triger <kostat@gmail.com> 
 * 
 * This file is part of Jaque 2 JPA - JAva QUEry 2 JPA library <http://code.google.com/p/jaque/>.
 * 
 * Jaque is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jaque is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jaque.jpa;

import java.lang.reflect.*;
import java.util.*;

import jaque.*;
import jaque.expressions.*;
import jaque.functions.Function;
import static jaque.Query.*;

final class QueryBinder extends SimpleExpressionVisitor {

	// private int _aliasCount;
	// IQueryProvider provider;
	private final Map<ParameterExpression, Expression> _map;
	private final Map<Expression, GroupByInfo> _groupByMap;
	private final Expression _root;

	private QueryBinder(/* IQueryProvider provider, */Expression root) {
		// this.provider = provider;
		_map = new HashMap<ParameterExpression, Expression>();
		_groupByMap = new HashMap<Expression, GroupByInfo>();
		_root = root;
	}

	static Expression bind(Expression expression) {
		return expression.apply(new QueryBinder(expression));
	}

	private boolean canBeColumn(Expression expression) {
		switch (expression.getExpressionType()) {
		// case DbExpressionType.Column:
		case DbExpressionType.Scalar:
		case DbExpressionType.Exists:
		case DbExpressionType.AggregateSubquery:
		case DbExpressionType.Aggregate:
			return true;
		default:
			return false;
		}
	}

	//
	// private ProjectedColumns projectColumns(Expression expression,
	// String newAlias, String... existingAliases) {
	// return ColumnProjector.projectColumns(
	// new Function<Boolean, Expression>() {
	// public Boolean invoke(Expression t) throws Throwable {
	// return canBeColumn(t);
	// }
	// }, expression, newAlias, existingAliases);
	// }

	// private static Expression stripQuotes(Expression e) {
	// while (e.getExpressionType() == ExpressionType.Quote)
	// e = ((UnaryExpression) e).getFirst();
	//
	// return e;
	// }

	// private String getNextAlias() {
	// return "t" + _aliasCount++;
	// }

	//
	// private ProjectionExpression VisitSequence(Expression source) {
	// // sure to call base.Visit in order to skip my override
	// return this.ConvertToSequence(source.apply(this)); // TODO
	// }

	// private ProjectionExpression ConvertToSequence(Expression expr) {
	// switch (expr.getExpressionType()) {
	// case DbExpressionType.Projection:
	// return (ProjectionExpression) expr;
	// // case ExpressionType.New: TODO
	// // NewExpression nex = (NewExpression)expr;
	// // if (expr.Type.IsGenericType &&
	// // expr.Type.GetGenericTypeDefinition() == typeof(Grouping<,>)) {
	// // return (ProjectionExpression)nex.Arguments[1];
	// // }
	// // goto default;
	// default:
	// throw new IllegalArgumentException("The expression of type '"
	// + expr.getResultType().toString() + "' is not a sequence");
	// }
	// }

	@Override
	public Expression visit(UnaryExpression e) {
		if (e.getExpressionType() == ExpressionType.Convert)
			return e.getFirst();
		return super.visit(e);
	}

	@Override
	public Expression visit(InvocationExpression e) {
		InvocableExpression ie = e.getMethod();
		if (ie.getExpressionType() == ExpressionType.MethodAccess) {
			MemberExpression me = (MemberExpression) ie;
			Method m = (Method) me.getMember();

			String methodName = m.getName();

			if (m.getDeclaringClass() == Operation.class) {
				if ("count".equals(methodName)) {
					// if (e.getArguments().size() == 1)
					// int i = 0;
					return this.BindAggregate(AggregateType.Count, Integer.TYPE,
							(SelectExpression) e.getArguments() // me.getResultType(),
									.get(0).apply(this));
				}
			} else if (m.getDeclaringClass() == Query.class)// (m.Method.DeclaringType
			// ==
			// typeof(Queryable) ||
			// m.Method.DeclaringType ==
			// typeof(Enumerable))
			{

				// switch () {
				if ("where".equals(methodName))
					return this.BindWhere(e.getArguments() // me.getResultType(),
							.get(1).apply(this),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(0).apply(this)));
				if ("select".equals(methodName)) {
					// JaqueEntityManager.JpaQueryable<?> jq =
					// (JaqueEntityManager.JpaQueryable<?>) getSource(
					// e).getValue();
					return this.BindSelect(e.getArguments().get(1).apply(this),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(0)));
				}
				if ("selectMany".equals(methodName)) {
					return this.BindSelect(e.getArguments().get(1).apply(this),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(0).apply(this)));
					// if (e.getArguments().size() == 2) {
					// return this.BindSelectMany(me.getResultType(), e
					// .getArguments().get(0),
					// (LambdaExpression<?>) stripQuotes(e
					// .getArguments().get(1)), null);
					// } else if (e.getArguments().size() == 3) {
					// return this.BindSelectMany(me.getResultType(), e
					// .getArguments().get(0),
					// (LambdaExpression<?>) stripQuotes(e
					// .getArguments().get(1)),
					// (LambdaExpression<?>) stripQuotes(e
					// .getArguments().get(2)));
					// }
				} else if ("Join".equals(methodName))
					return this.BindJoin(me.getResultType(), e.getArguments()
							.get(0), e.getArguments().get(1),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(2)),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(3)),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(4)));
				if ("OrderBy".equals(methodName))
					return this.BindOrderBy(me.getResultType(), e
							.getArguments().get(0),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(1)), true);
				if ("OrderByDescending".equals(methodName))
					return this.BindOrderBy(me.getResultType(), e
							.getArguments().get(0),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(1)), false);
				if ("ThenBy".equals(methodName))
					return this.BindThenBy(e.getArguments().get(0),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(1)), true);
				if ("ThenByDescending".equals(methodName))
					return this.BindThenBy(e.getArguments().get(0),
							(LambdaExpression<?>) stripQuotes(e.getArguments()
									.get(1)), false);
				if ("GroupBy".equals(methodName)) {
					if (e.getArguments().size() == 2) {
						return this.BindGroupBy(e.getArguments().get(0),
								(LambdaExpression<?>) stripQuotes(e
										.getArguments().get(1)), null, null);
					} else if (e.getArguments().size() == 3) {
						LambdaExpression<?> lambda1 = (LambdaExpression<?>) stripQuotes(e
								.getArguments().get(1));
						LambdaExpression<?> lambda2 = (LambdaExpression<?>) stripQuotes(e
								.getArguments().get(2));
						if (lambda2.getParameters().size() == 1) {
							// second lambda is element selector
							return this.BindGroupBy(e.getArguments().get(0),
									lambda1, lambda2, null);
						} else if (lambda2.getParameters().size() == 2) {
							// second lambda is result selector
							return this.BindGroupBy(e.getArguments().get(0),
									lambda1, null, lambda2);
						}
					} else if (e.getArguments().size() == 4) {
						return this.BindGroupBy(e.getArguments().get(0),
								(LambdaExpression<?>) stripQuotes(e
										.getArguments().get(1)),
								(LambdaExpression<?>) stripQuotes(e
										.getArguments().get(2)),
								(LambdaExpression<?>) stripQuotes(e
										.getArguments().get(3)));
					}
					// TODO
					// } else if ("Count".equals(methodName)
					// || "Min".equals(methodName) || "Max".equals(methodName)
					// || "Sum".equals(methodName)
					// || "Average".equals(methodName)) {
					// if (e.getArguments().size() == 1) {
					// return this.BindAggregate(e.getArguments().get(0), m,
					// null, e == _root); // TODO
					// } else if (e.getArguments().size() == 2) {
					// LambdaExpression<?> selector = (LambdaExpression<?>)
					// stripQuotes(e
					// .getArguments().get(1));
					// return this.BindAggregate(e.getArguments().get(0), m,
					// selector, e == _root); // TODO
					// }
					// } else if ("Distinct".equals(methodName)) {
					// if (e.getArguments().size() == 1) {
					// return this.BindDistinct(e.getArguments().get(0));
					// }
					// } else if ("Skip".equals(methodName)) {
					// if (e.getArguments().size() == 2) {
					// return this.BindSkip(e.getArguments().get(0), e
					// .getArguments().get(1));
					// }
					// } else if ("Take".equals(methodName)) {
					// if (e.getArguments().size() == 2) {
					// return this.BindTake(e.getArguments().get(0), e
					// .getArguments().get(1));
					// }
					// } else if ("First".equals(methodName)
					// || "FirstOrDefault".equals(methodName)
					// || "Single".equals(methodName)
					// || "SingleOrDefault".equals(methodName)) {
					// if (e.getArguments().size() == 1) {
					// return this.BindFirst(e.getArguments().get(0), null,
					// methodName, e == _root); // TODO
					// } else if (e.getArguments().size() == 2) {
					// LambdaExpression<?> predicate = (LambdaExpression<?>)
					// stripQuotes(e
					// .getArguments().get(1));
					// return this.BindFirst(e.getArguments().get(0),
					// predicate, methodName, e == _root); // TODO
					// }
				} else if ("Any".equals(methodName)) {
					if (e.getArguments().size() == 1) {
						return this.BindAnyAll(e.getArguments().get(0), m,
								null, e == _root); // TODO
					} else if (e.getArguments().size() == 2) {
						LambdaExpression<?> predicate = (LambdaExpression<?>) stripQuotes(e
								.getArguments().get(1));
						return this.BindAnyAll(e.getArguments().get(0), m,
								predicate, e == _root); // TODO
					}
				} else if ("All".equals(methodName)) {
					if (e.getArguments().size() == 2) {
						LambdaExpression<?> predicate = (LambdaExpression<?>) stripQuotes(e
								.getArguments().get(1));
						return this.BindAnyAll(e.getArguments().get(0), m,
								predicate, e == _root); // TODO
					}
					// } else if ("Contains".equals(methodName)) {
					// if (e.getArguments().size() == 2) {
					// return this.BindContains(e.getArguments().get(0), e
					// .getArguments().get(1), e == _root);
					// }
				}
			}
		}
		return super.visit(e);
	}

	private Expression BuildPredicateWithNullsEqual(
			Iterable<Expression> source1, Iterable<Expression> source2) {
		Iterator<Expression> en1 = source1.iterator();
		Iterator<Expression> en2 = source2.iterator();
		Expression result = null;
		while (en1.hasNext() && en2.hasNext()) {
			Expression e1 = en1.next();
			Expression e2 = en2.next();
			Expression compare = Expression.logicalOr(Expression.logicalAnd(
					Expression.isNull(e1), Expression.isNull(e2)), Expression
					.equal(e1, e2));
			result = (result == null) ? compare : Expression.logicalAnd(result,
					compare);
		}
		return result;
	}

	Expression _currentGroupElement;

	private static final class GroupByInfo {
		private final String _alias;
		private final Expression _element;

		String getAlias() {
			return _alias;
		}

		Expression getElement() {
			return _element;
		}

		GroupByInfo(String alias, Expression element) {
			_alias = alias;
			_element = element;
		}
	}

	private static AggregateType GetAggregateType(String methodName) {
		return AggregateType.valueOf(AggregateType.class, methodName);
		// switch (methodName) {
		// case "Count": return AggregateType.Count;
		// case "Min": return AggregateType.Min;
		// case "Max": return AggregateType.Max;
		// case "Sum": return AggregateType.Sum;
		// case "Average": return AggregateType.Average;
		// default: throw new Exception(string.Format("Unknown aggregate type:
		// {0}", methodName));
		// }
	}

	private static boolean HasPredicateArg(AggregateType aggregateType) {
		return aggregateType == AggregateType.Count;
	}

	//
	// private Expression BindDistinct(Expression source) {
	// ProjectionExpression projection = this.VisitSequence(source);
	// SelectExpression select = projection.getSource();
	// String alias = this.getNextAlias();
	// ProjectedColumns pc = this.projectColumns(projection.getProjector(),
	// alias, projection.getSource().getAlias());
	// return new ProjectionExpression(new SelectExpression(select
	// .getResultType(), alias, /*pc.getColumns(), */projection
	// .getSource(), null, null, null, true, null, null), pc
	// .getProjector());
	// }
	//
	// private Expression BindTake(Expression source, Expression take) {
	// ProjectionExpression projection = this.VisitSequence(source);
	// take = take.apply(this);
	// SelectExpression select = projection.getSource();
	// String alias = this.getNextAlias();
	// ProjectedColumns pc = this.projectColumns(projection.getProjector(),
	// alias, projection.getSource().getAlias());
	// return new ProjectionExpression(new SelectExpression(select
	// .getResultType(), alias, /*pc.getColumns(), */projection
	// .getSource(), null, null, null, false, null, take), pc
	// .getProjector());
	// }
	//
	// private Expression BindSkip(Expression source, Expression skip) {
	// ProjectionExpression projection = this.VisitSequence(source);
	// skip = skip.apply(this);
	// SelectExpression select = projection.getSource();
	// String alias = this.getNextAlias();
	// ProjectedColumns pc = this.projectColumns(projection.getProjector(),
	// alias, projection.getSource().getAlias());
	// return new ProjectionExpression(new SelectExpression(select
	// .getResultType(), alias, /*pc.getColumns(), */projection
	// .getSource(), null, null, null, false, skip, null), pc
	// .getProjector());
	// }
	//
	// private Expression BindFirst(Expression source,
	// LambdaExpression<?> predicate, String kind, boolean isRoot) {
	// ProjectionExpression projection = this.VisitSequence(source);
	// Expression where = null;
	// if (predicate != null) {
	// _map.put(predicate.getParameters().get(0), projection
	// .getProjector());
	// where = predicate.getBody().apply(this);
	// }
	// Expression take = kind.startsWith("First") ? Expression.constant(1)
	// : null;
	// if (take != null || where != null) {
	// String alias = this.getNextAlias();
	// ProjectedColumns pc = this.projectColumns(
	// projection.getProjector(), alias, projection.getSource()
	// .getAlias());
	// projection = new ProjectionExpression(new SelectExpression(source
	// .getResultType(), alias, /*pc.getColumns(), */projection
	// .getSource(), where, null, null, false, null, take), pc
	// .getProjector());
	// }
	// if (isRoot) {
	// Class<?> elementType = projection.getProjector().getResultType();
	// ParameterExpression p = null; // TODO
	// //
	// Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(elementType),
	// // "p");
	// LambdaExpression<?> gator = null; // TODO
	// // Expression.lambda(Expression.Call(typeof(Enumerable),
	// // kind, new Type[] {
	// // elementType }, p), p);
	// return new ProjectionExpression(projection.getSource(), projection
	// .getProjector(), gator);
	// }
	// return projection;
	// }

	private Expression BindAnyAll(Expression source, Method method,
			LambdaExpression<?> predicate, boolean isRoot) {
		boolean isAll = method.getName().equals("All");
		ConstantExpression constSource = (source.getExpressionType() == ExpressionType.Constant) ? (ConstantExpression) source
				: null;
		if (constSource != null && !IsTable(constSource)) {
			assert (!isRoot);
			Expression where = null;
			for (Object value : (Iterable<Object>) constSource.getValue()) {
				Expression expr = Expression.invoke(predicate, Expression
						.constant(value, predicate.getParameters().get(0)
								.getResultType()));
				if (where == null) {
					where = expr;
				} else if (isAll) {
					where = Expression.logicalAnd(where, expr);
				} else {
					where = Expression.logicalOr(where, expr);
				}
			}
			return where.apply(this);
		} else {
			// if (isAll) {
			// predicate = Expression.Lambda(Expression.Not(predicate.Body),
			// predicate.Parameters.ToArray());
			// }
			// if (predicate != null) {
			// source = Expression.Call(typeof(Queryable), "Where",
			// method.GetGenericArguments(), source, predicate);
			// }
			// ProjectionExpression projection = this.VisitSequence(source);
			// Expression result = new ExistsExpression(projection.Source);
			// if (isAll) {
			// result = Expression.Not(result);
			// }
			// if (isRoot) {
			// return GetSingletonSequence(result, "SingleOrDefault");
			// }
			// return result;
			return null; // TODO
		}
	}

	//
	// private Expression BindContains(Expression source, Expression match,
	// boolean isRoot) {
	// ConstantExpression constSource = (source.getExpressionType() ==
	// ExpressionType.Conditional) ? (ConstantExpression) source
	// : null;
	// if (constSource != null && !IsTable(constSource)) {
	// assert (!isRoot);
	// List<Expression> values = new ArrayList<Expression>();
	// for (Object value : (Iterable<Object>) constSource.getValue()) {
	// values.add(Expression.constant(value, match.getResultType())); // TODO
	// // Convert.ChangeType(value,
	// // match.Type)
	// }
	// match = match.apply(this);
	// return new InExpression(match, values);
	// } else {
	// ProjectionExpression projection = this.VisitSequence(source);
	// match = match.apply(this);
	// Expression result = new InExpression(match, projection.getSource());
	// if (isRoot) {
	// return this.GetSingletonSequence(result, "SingleOrDefault");
	// }
	// return result;
	// }
	// }

	// private Expression GetSingletonSequence(Expression expr, String
	// aggregator) {
	// ParameterExpression p = null; // TODO
	// // Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(expr.Type),
	// // "p");
	// LambdaExpression gator = null;
	// if (aggregator != null) {
	// gator = null; // TODO
	// // Expression.Lambda(Expression.Call(typeof(Enumerable),
	// // aggregator, new Type[] { expr.Type }, p), p);
	// }
	// String alias = this.getNextAlias();
	// SelectExpression select = new SelectExpression(p.getResultType(),
	// alias,
	// /*Arrays.asList(new ColumnDeclaration[] { new ColumnDeclaration(
	// "value", expr) }), */null, null);
	// return new ProjectionExpression(select, new ColumnExpression(expr
	// .getResultType(), alias, "value"), gator);
	// }

	@Override
	public Expression visit(ConstantExpression e) {
		// if (IsTable(e))
		// // getT
		// return GetTableProjection(((JaqueEntityManager.JpaQueryable<?>) e
		// .getValue()).getEntityType());
		// TODO Auto-generated method stub
		return super.visit(e);
	}

	// private ProjectionExpression GetTableProjection(Class<?> rowType) {
	// String tableAlias = this.getNextAlias();
	// String selectAlias = this.getNextAlias();
	// //List<MemberBinding> bindings = new List<MemberBinding>();
	// // List<ColumnDeclaration> columns = new ArrayList<ColumnDeclaration>();
	// // for (Member mi : this.GetMappedMembers(rowType)) {
	// // String columnName = this.GetColumnName(mi);
	// // Type columnType = this.GetColumnType(mi);
	// // bindings.Add(Expression.Bind(mi, new ColumnExpression(columnType,
	// selectAlias, columnName)));
	// // columns.Add(new ColumnDeclaration(columnName, new
	// ColumnExpression(columnType, tableAlias, columnName)));
	// // }
	// //Expression projector = Expression.MemberInit(Expression.New(rowType),
	// bindings);
	// //Type resultType = typeof(IEnumerable<>).MakeGenericType(rowType);
	// return new ProjectionExpression(
	// new SelectExpression(Iterable.class, selectAlias, /*columns, */new
	// TableExpression(rowType, tableAlias), null),
	// null //projector
	// );
	// }

	private boolean IsTable(Expression expression) {
		return expression.getResultType() == Queryable.class;// true;
		// //TODO
		// expression.Type.IsGenericType
		// &&
		// expression.Type.GetGenericTypeDefinition()
		// ==
		// typeof(Query<>);
	}

	private String GetTableName(Class<?> rowType) {
		return rowType.getName();
	}

	private String GetColumnName(Member member) {
		return member.getName();
	}

	private SelectExpression BindWhere(
	/* Class<?> resultType, */Expression source, LambdaExpression<?> predicate) {
		// return null;
		return new SelectExpression(source, /* alias, */null, // expression,
				predicate.getBody());
		// ProjectionExpression projection = this.VisitSequence(source);
		// _map.put(predicate.getParameters().get(0),
		// projection.getProjector());
		// Expression where = predicate.getBody().apply(this);
		// String alias = this.getNextAlias();
		// ProjectedColumns pc = this.projectColumns(projection.getProjector(),
		// alias, projection.getSource().getAlias());
		// return new ProjectionExpression(new SelectExpression(resultType,
		// alias,
		// /*pc.getColumns(), */projection.getSource(), where), pc
		// .getProjector());
	}

	private Expression BindAggregate(AggregateType type, Class<?> returnType,
			SelectExpression source) {

		return new SelectExpression(source.getFrom(), new AggregateExpression(
				returnType, type, source.getSelector(), source
						.isDistinct()), source.getWhere(), source.getOrderBy(),
				source.getGroupBy(), false);

	}

	private SelectExpression BindSelect(Expression source,
			LambdaExpression<?> selector) {
		// Expression expression = selector.getBody().apply(this);
		// String alias = this.getNextAlias();

		// Expression.invoke(
		// selector,
		// new NamedValueExpression(e.getAlias(), Expression.constant(
		// null, e.getEntityType())))
		// Expression projector = Expression.constant(alias);
		return new SelectExpression(source, /* alias, */selector.getBody(), // expression,
				null);
		// ProjectionExpression projection = this.VisitSequence(source);
		// _map.put(selector.getParameters().get(0), projection.getProjector());
		// Expression expression = selector.getBody().apply(this);
		// String alias = this.getNextAlias();
		// ProjectedColumns pc = this.projectColumns(expression, alias,
		// projection
		// .getSource().getAlias());
		// return new ProjectionExpression(new SelectExpression(resultType,
		// alias,
		// /*pc.getColumns(), */projection.getSource(), null), pc
		// .getProjector());
	}

	protected Expression BindSelectMany(Class<?> resultType, Expression source,
			LambdaExpression<?> collectionSelector,
			LambdaExpression<?> resultSelector) {
		return null;
		// ProjectionExpression projection = this.VisitSequence(source);
		// _map.put(collectionSelector.getParameters().get(0), projection
		// .getProjector());
		// ProjectionExpression collectionProjection = (ProjectionExpression)
		// this
		// .VisitSequence(collectionSelector.getBody());
		// JoinType joinType = IsTable(collectionSelector.getBody()) ?
		// JoinType.CrossJoin
		// : JoinType.CrossJoin; // TODO: CrossApply
		// JoinExpression join = new JoinExpression(resultType, joinType,
		// projection.getSource(), collectionProjection.getSource(), null);
		// String alias = this.getNextAlias();
		// ProjectedColumns pc;
		// if (resultSelector == null) {
		// pc = this.projectColumns(collectionProjection.getProjector(),
		// alias, projection.getSource().getAlias(),
		// collectionProjection.getSource().getAlias());
		// } else {
		// _map.put(resultSelector.getParameters().get(0), projection
		// .getProjector());
		// _map.put(resultSelector.getParameters().get(1),
		// collectionProjection.getProjector());
		// Expression result = resultSelector.getBody().apply(this);
		// pc = this.projectColumns(result, alias, projection.getSource()
		// .getAlias(), collectionProjection.getSource().getAlias());
		// }
		// return new ProjectionExpression(new SelectExpression(resultType,
		// alias,
		// /*pc.getColumns(), */join, null), pc.getProjector());
	}

	protected Expression BindJoin(Class<?> resultType, Expression outerSource,
			Expression innerSource, LambdaExpression<?> outerKey,
			LambdaExpression<?> innerKey, LambdaExpression<?> resultSelector) {
		return null;
		// ProjectionExpression outerProjection =
		// this.VisitSequence(outerSource);
		// ProjectionExpression innerProjection =
		// this.VisitSequence(innerSource);
		// _map.put(outerKey.getParameters().get(0), outerProjection
		// .getProjector());
		// // this.map[outerKey.Parameters[0]] = outerProjection.Projector;
		// Expression outerKeyExpr = outerKey.getBody().apply(this);
		// _map.put(innerKey.getParameters().get(0), innerProjection
		// .getProjector());
		// // this.map[innerKey.Parameters[0]] = innerProjection.Projector;
		// Expression innerKeyExpr = innerKey.getBody().apply(this);
		// _map.put(resultSelector.getParameters().get(0), outerProjection
		// .getProjector());
		// _map.put(resultSelector.getParameters().get(1), innerProjection
		// .getProjector());
		// // this.map[resultSelector.Parameters[0]] =
		// outerProjection.Projector;
		// // this.map[resultSelector.Parameters[1]] =
		// innerProjection.Projector;
		// Expression resultExpr = resultSelector.getBody().apply(this);
		// JoinExpression join = new JoinExpression(resultType,
		// JoinType.InnerJoin, outerProjection.getSource(),
		// innerProjection.getSource(), Expression.equal(outerKeyExpr,
		// innerKeyExpr));
		// String alias = this.getNextAlias();
		// ProjectedColumns pc = this.projectColumns(resultExpr, alias,
		// outerProjection.getSource().getAlias(), innerProjection
		// .getSource().getAlias());
		// return new ProjectionExpression(new SelectExpression(resultType,
		// alias,
		// /*pc.getColumns(), */join, null), pc.getProjector());
	}

	List<OrderExpression> thenBys;

	protected Expression BindOrderBy(Class<?> resultType, Expression source,
			LambdaExpression<?> orderSelector, boolean isAscending) {
		return null;
		// List<OrderExpression> myThenBys = this.thenBys;
		// this.thenBys = null;
		// ProjectionExpression projection = this.VisitSequence(source);
		//
		// _map.put(orderSelector.getParameters().get(0), projection
		// .getProjector());
		// // this.map[orderSelector.Parameters[0]] = projection.Projector;
		// List<OrderExpression> orderings = new ArrayList<OrderExpression>();
		// orderings
		// .add(new OrderExpression(isAscending, orderSelector.getBody()));
		//
		// if (myThenBys != null) {
		// for (int i = myThenBys.size() - 1; i >= 0; i--) {
		// OrderExpression tb = myThenBys.get(i);
		// LambdaExpression<?> lambda = (LambdaExpression<?>) tb
		// .getExpression();
		// _map.put(lambda.getParameters().get(0), projection
		// .getProjector());
		// // this.map[lambda.Parameters[0]] = projection.Projector;
		// orderings.add(new OrderExpression(tb.isAscending(), lambda
		// .getBody()));
		// }
		// }
		//
		// String alias = this.getNextAlias();
		// ProjectedColumns pc = this.projectColumns(projection.getProjector(),
		// alias, projection.getSource().getAlias());
		// return new ProjectionExpression(new SelectExpression(resultType,
		// alias,
		// /*pc.getColumns(), */projection.getSource(), null, Collections
		// .unmodifiableList(orderings), null), pc.getProjector());
	}

	protected Expression BindThenBy(Expression source,
			LambdaExpression<?> orderSelector, boolean isAcending) {
		if (this.thenBys == null) {
			this.thenBys = new ArrayList<OrderExpression>();
		}
		this.thenBys.add(new OrderExpression(isAcending, orderSelector));
		return source.apply(this);
	}

	protected Expression BindGroupBy(Expression source,
			LambdaExpression<?> keySelector,
			LambdaExpression<?> elementSelector,
			LambdaExpression<?> resultSelector) {
		return null;
		// ProjectionExpression projection = this.VisitSequence(source);
		//
		// _map.put(keySelector.getParameters().get(0),
		// projection.getProjector());
		// // this.map[keySelector.Parameters[0]] = projection.Projector;
		// Expression keyExpr = keySelector.getBody().apply(this);
		//
		// Expression elemExpr = projection.getProjector();
		// if (elementSelector != null) {
		// _map.put(elementSelector.getParameters().get(0), projection
		// .getProjector());
		// // this.map[elementSelector.Parameters[0]] = projection.Projector;
		// elemExpr = elementSelector.getBody().apply(this);
		// }
		//
		// // Use ProjectColumns to get group-by expressions from key expression
		// ProjectedColumns keyProjection = this.projectColumns(keyExpr,
		// projection.getSource().getAlias(), projection.getSource()
		// .getAlias());
		// Iterable<Expression> groupExprs = select(keyProjection.getColumns(),
		// // .Select(c
		// // =>
		// // c.Expression);
		// new Function<Expression, ColumnDeclaration>() {
		// public Expression invoke(ColumnDeclaration t)
		// throws Throwable {
		// return t.getExpression();
		// }
		// });
		//
		// // make duplicate of source query as basis of element subquery by
		// // visiting the source again
		// ProjectionExpression subqueryBasis = this.VisitSequence(source);
		//
		// // recompute key columns for group expressions relative to subquery
		// // (need these for doing the correlation predicate)
		// _map.put(keySelector.getParameters().get(0), subqueryBasis
		// .getProjector());
		// // this.map[keySelector.Parameters[0]] = subqueryBasis.Projector;
		// Expression subqueryKey = keySelector.getBody().apply(this);
		//
		// // use same projection trick to get group-by expressions based on
		// // subquery
		// ProjectedColumns subqueryKeyPC = this.projectColumns(subqueryKey,
		// subqueryBasis.getSource().getAlias(), subqueryBasis.getSource()
		// .getAlias());
		// Iterable<Expression> subqueryGroupExprs = select(subqueryKeyPC
		// .getColumns(), // .Select(c => c.Expression);
		// new Function<Expression, ColumnDeclaration>() {
		// public Expression invoke(ColumnDeclaration t)
		// throws Throwable {
		// return t.getExpression();
		// }
		// });
		// Expression subqueryCorrelation = this.BuildPredicateWithNullsEqual(
		// subqueryGroupExprs, groupExprs);
		//
		// // compute element based on duplicated subquery
		// Expression subqueryElemExpr = subqueryBasis.getProjector();
		// if (elementSelector != null) {
		// _map.put(elementSelector.getParameters().get(0), subqueryBasis
		// .getProjector());
		// // this.map[elementSelector.Parameters[0]] =
		// // subqueryBasis.Projector;
		// subqueryElemExpr = elementSelector.getBody().apply(this);
		// }
		//
		// // build subquery that projects the desired element
		// String elementAlias = this.getNextAlias();
		// ProjectedColumns elementPC = this.projectColumns(subqueryElemExpr,
		// elementAlias, subqueryBasis.getSource().getAlias());
		// ProjectionExpression elementSubquery = null;
		// // TODO
		// // new ProjectionExpression(
		// // new
		// //
		// SelectExpression(TypeSystem.GetSequenceType(subqueryElemExpr.Type),
		// // elementAlias, elementPC.Columns, subqueryBasis.Source,
		// // subqueryCorrelation),
		// // elementPC.Projector
		// // );
		//
		// String alias = this.getNextAlias();
		//
		// // make it possible to tie aggregates back to this group-by
		// GroupByInfo info = new GroupByInfo(alias, elemExpr);
		// _groupByMap.put(elementSubquery, info);
		//
		// Expression resultExpr;
		// if (resultSelector != null) {
		// Expression saveGroupElement = this.currentGroupElement;
		// this.currentGroupElement = elementSubquery;
		// // compute result expression based on key & element-subquery
		// _map.put(resultSelector.getParameters().get(0), keyProjection
		// .getProjector());
		// // this.map[resultSelector.Parameters[0]] = keyProjection.Projector;
		// _map.put(resultSelector.getParameters().get(1), elementSubquery);
		// // this.map[resultSelector.Parameters[1]] = elementSubquery;
		// resultExpr = resultSelector.getBody().apply(this);
		// this.currentGroupElement = saveGroupElement;
		// } else {
		// // result must be IGrouping<K,E>
		// resultExpr = null;
		// // TODO
		// // Expression.New(
		// // typeof(Grouping<,>).MakeGenericType(keyExpr.Type,
		// // subqueryElemExpr.Type).GetConstructors()[0],
		// // new Expression[] { keyExpr, elementSubquery }
		// // );
		// }
		//
		// ProjectedColumns pc = this.projectColumns(resultExpr, alias,
		// projection
		// .getSource().getAlias());
		//
		// // make it possible to tie aggregates back to this group-by
		// Expression projectedElementSubquery = null;// TODO
		// // ((NewExpression)pc.Projector).Arguments[1];
		// _groupByMap.put(projectedElementSubquery, info);
		//
		// return new ProjectionExpression(null, // TODO: new
		// // SelectExpression(TypeSystem.GetSequenceType(resultExpr.Type),
		// // alias, pc.Columns,
		// // projection.Source, null,
		// // null, groupExprs),
		// pc.getProjector());
	}

	Expression currentGroupElement;
}
